#region Licence
// Copyright (c) 2008 Tasman Logic Ltd

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Reflection;
using System.Reflection.Emit;
using ActiveSharp.Inspection;
using NUnit.Framework;

namespace ActiveSharp.Tests
{
    [TestFixture]
    public class MsilInspectionTests
    {
        [Test]
        public void CanProcessMsil()
        {
            // process, detecting that the call we are looking for is not there
            FindWriteLineFoo("TestMethodWithWriteLineInt", false);

            // this time, the call is there but the parameter we are looking for (in this test) is not
            FindWriteLineFoo("TestMethodWithWriteLineString", false);

            // this time, we have the call and the param that we are looking for in this test
            FindWriteLineFoo("TestMethodWithWriteLineOfFoo", true);
        }

        void FindWriteLineFoo(string targetMethodName, bool expectToFind)
        {
            MethodInfo targetMethod = typeof(TestClass).GetMethod(targetMethodName);
            MethodInspector inspector = new MethodInspector(targetMethod);

            // in this test code, we are looking for a call to Console.WriteLine(string)
            MethodInfo writeLineMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
            bool foundWriteLine = false;

            foreach(Instruction current in inspector.Instructions)
            {
                if (current.OpCode == OpCodes.Call && current.Operand.AsMethod == writeLineMethod)
                {
                    // we have found a call to Console writeline
                    // Now, insist that the param passed to it is the string literal "Foo"
                    Instruction previous = current.Offset(-1);
                    if (previous.OpCode == OpCodes.Ldstr && previous.Operand.AsString == "Foo")
                        foundWriteLine = true;
                }
            }

            Assert.AreEqual(expectToFind, foundWriteLine);
        }

        [Test]
        public void CanSeeIfStringLiteralIsContained()
        {
            MethodInfo targetMethod = typeof(TestClass).GetMethod("TestMethodWithWriteLineOfFoo");
            MethodInspector inspector = new MethodInspector(targetMethod);
            Assert.IsTrue(inspector.ContainsStringLiteral("Foo"));

            targetMethod = typeof(TestClass).GetMethod("TestMethodWithWriteLineInt");
            inspector = new MethodInspector(targetMethod);
            Assert.IsFalse(inspector.ContainsStringLiteral("Foo"));
        }

        [Test]
        public void CanSeeIfMethodIsCalled()
        {
            MethodInfo writeLineInt = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) });

            MethodInfo targetMethod = typeof(TestClass).GetMethod("TestMethodWithWriteLineOfFoo");
            MethodInspector inspector = new MethodInspector(targetMethod);
            Assert.IsFalse(inspector.ContainsCallTo(writeLineInt));

            targetMethod = typeof(TestClass).GetMethod("TestMethodWithWriteLineInt");
            inspector = new MethodInspector(targetMethod);
            Assert.IsTrue(inspector.ContainsCallTo(writeLineInt));
        }

        [Test]
        public void CanSeeIfConstructorIsCalled()
        {
            MethodBase listConstructor = typeof(List).GetConstructor(new Type[] { });

            MethodInfo targetMethod = typeof(TestClass).GetMethod("TestMethodWithConstructorCall");
            MethodInspector inspector = new MethodInspector(targetMethod);
            Assert.IsTrue(inspector.ContainsCallTo(listConstructor));
        }


        [Test]
        public void CanGetBaseTargetMethodsForVirtualCalls()
        {
            MethodInfo targetMethod = typeof(TestClass).GetMethod("TestMethodWithVirtualCall");
            MethodInspector inspector = new MethodInspector(targetMethod);

            MethodInfo methodWeAreLookingFor = typeof(TestBase).GetMethod("TestMethod");
            bool found = false;

            foreach (Instruction current in inspector.Instructions)
            {
                if (current.OpCode == OpCodes.Callvirt && current.Operand.AsMethod == methodWeAreLookingFor)
                {
                    found = true;
                }
            }

            Assert.IsTrue(found);
        }

        
        public class TestClass
        {
            public void TestMethodWithWriteLineInt()
            {
                Console.WriteLine(5);
            }

            public void TestMethodWithWriteLineString()
            {
                Console.WriteLine("Bar");
            }

            public void TestMethodWithWriteLineOfFoo()
            {
                Console.WriteLine("Foo");
            }

            TestBase _targetForVirtualCall = new Derived();
        
            public string TestMethodWithVirtualCall()
            {
                return _targetForVirtualCall.TestMethod();  
            }

            public object TestMethodWithConstructorCall()
            {
                return new List();
            }
       }

       public class TestBase
       {
           public virtual string TestMethod()
           {
               return "base";
           }
       }

       public class Derived : TestBase
       {
           public override string TestMethod()
           {
               return "derived";
           }

       }
    }
}
